#include "zipwriter.h"

const char* pk_signature = "\x50\x4b\x03\x04";
const char* cd_signature = "\x50\x4b\x01\x02";
const char* eocd_signature = "\x50\x4b\x05\x06";

void ZipWriter::writeZip(const char* file, const char* dest)
{
    FileHeader* fheader = fileDataExtractor->createFileHeader(file);
    CentralDirectory* cdirectory = fileDataExtractor->create_cdirectory(fheader);

    if (fheader && cdirectory)
    {
        unsigned int fheader_len = 30 + fheader->name_len + fheader->extrafield_len + fheader->size_comp;
        fheader->header_len = fheader_len;
        char * fheader_buffer = (char*) malloc(sizeof (char) * fheader_len);
        appendFileHeader(fheader_buffer, fheader);

        unsigned int centraldir_len = 46 + fheader->name_len + fheader->extrafield_len + cdirectory->commentLenght_;
        char * cdir_buffer = (char*) malloc(sizeof (char) * centraldir_len);
        appendCentralDirectory(cdir_buffer, cdirectory);

        FILE* zipfile = fopen(dest, "wb");

        if (zipfile)
        {
            fwrite(fheader_buffer, sizeof (char), fheader_len, zipfile);
            fwrite(cdir_buffer, sizeof (char), centraldir_len, zipfile);
            writeEOCentralDirectory(zipfile, centraldir_len, fheader_len);
        }
        else
            printf("can't open %s\n", dest);

        if (fheader)
            free(fheader);
        if (fheader_buffer)
            free(fheader_buffer);
        if (cdirectory)
            free(cdirectory);
        if (cdir_buffer)
            free(cdir_buffer);
        if (zipfile)
            fclose(zipfile);
    }
    else
        printf("can't create fileheader and central directory entries %s\n", file);
}

void ZipWriter::append_signature(char * buffer, const char* signature)
{
    for (size_t index = 0; index < strlen(signature); index++)
        buffer[index] = signature[index];
}

void ZipWriter::appendFileHeader(char *buffer, FileHeader* header)
{
    append_signature(buffer, pk_signature);

    append<short>(buffer, header->version_, 4);
    append<short>(buffer, header->flags_, 6);
    append<short>(buffer, header->method_, 8);
    append<short>(buffer, header->time_, 10);
    append<short>(buffer, header->date_, 12);
    append<int>(buffer, header->crc32_, 14);
    append<int>(buffer, header->size_comp, 18);
    append<int>(buffer, header->size_uncomp, 22);
    append<short>(buffer, header->name_len, 26);
    append<short>(buffer, header->extrafield_len, 28);

    appendFileName(buffer, header->name_, header->name_len, 30);
    appendContent(buffer, header->content_, header->size_comp, 30 + header->name_len);
}

void ZipWriter::appendContent(char* buffer, unsigned char* content, size_t content_len, size_t offset)
{
    size_t i;
    for (i = 0; i < content_len; i++)
        buffer[offset + i] = content[i];
}

template <class t>
void ZipWriter::append(char* buffer, t value, size_t offset)
{
    const char* data = reinterpret_cast<const char *> (&value);
    size_t i;
    for (i = 0; i < sizeof (value); i++)
        buffer[offset + i] = data[i];
}

void ZipWriter::appendFileName(char* buffer, char* name, size_t name_len, size_t offset)
{
    size_t i;
    for (i = 0; i < name_len; i++)
        buffer[offset + i] = name[i];
}

void ZipWriter::appendCentralDirectory(char* cdir_buffer, CentralDirectory* cDirectory)
{
    FileHeader* header = cDirectory->header;
    append_signature(cdir_buffer, cd_signature);
    append<short>(cdir_buffer, cDirectory->versionMadeBy_, 4);
    append<short>(cdir_buffer, header->version_, 6);
    append<short>(cdir_buffer, header->flags_, 8);
    append<short>(cdir_buffer, header->method_, 10);
    append<short>(cdir_buffer, header->time_, 12);
    append<short>(cdir_buffer, header->date_, 14);
    append<int>(cdir_buffer, header->crc32_, 16);
    append<int>(cdir_buffer, header->size_comp, 20);
    append<int>(cdir_buffer, header->size_uncomp, 24);
    append<short>(cdir_buffer, header->name_len, 28);
    append<short>(cdir_buffer, header->extrafield_len, 30);
    append<short>(cdir_buffer, cDirectory->commentLenght_, 32);
    append<short>(cdir_buffer, cDirectory->diskStart_, 34);
    append<short>(cdir_buffer, cDirectory->intAttr_, 36);
    append<int>(cdir_buffer, cDirectory->extAttr_, 38);
    append<int>(cdir_buffer, cDirectory->offset_, 42);

    appendFileName(cdir_buffer, header->name_, header->name_len, 46);
}

void ZipWriter::writeEOCentralDirectory(FILE* dest, unsigned int cdlen, unsigned int cdoffset)
{
    unsigned int eocentraldir_len = 22;
    char * buffer = (char*) malloc(sizeof (char) * eocentraldir_len);
    append_signature(buffer, eocd_signature);
    append<short>(buffer, 0, 4);
    append<short>(buffer, 0, 6);
    append<short>(buffer, 1, 8);
    append<short>(buffer, 1, 10);
    append<int>(buffer, cdlen, 12);
    append<int>(buffer, cdoffset, 16);
    append<short>(buffer, 0, 20);

    size_t writed = fwrite(buffer, sizeof (char), eocentraldir_len, dest);

    if (writed != eocentraldir_len)
        printf("error writting end of central directory\n");
    free(buffer);
}